Skip to content

perf: mirror ASOF equality-key filters to right - #25574

Merged
jayzhan211 merged 7 commits into
apache:mainfrom
Xuanwo:xuanwo/asof-key-filter-mirroring
Sep 26, 2026
Merged

jayzhan211 merged 7 commits into
apache:mainfrom
Xuanwo:xuanwo/asof-key-filter-mirroring

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

When a filter selects one ASOF equality-key group on the left, rows in other right-side groups cannot match any surviving left row. The broadcast join still sorts and retains those right rows unless the same key filter is applied to the right input.

Performance

Q09 has 50M rows on each side across 500K equality groups. WHERE l.key = 42 retains one group and produces 100 rows with both revisions. On an Apple M4 Max, using independent release builds and two 7-iteration runs per revision, the median elapsed time across all 14 iterations was:

Revision Median elapsed
main without this optimization (92559afc0) 972.1 ms
This change (c7522fa37) 78.5 ms

This selective keyed workload is about 12.4x faster (91.9% lower elapsed time). The benchmark file and command were the same for both binaries:

target/release/benchmark_runner asof_join --query 9 --iterations 7

What changes are included in this PR?

  • Mirror a pushed left_key = literal predicate to the corresponding right key when both join keys are direct columns of the same type.
  • Keep the original left filter and ASOF null-padding behavior unchanged. Other predicate shapes and coerced or expression-based keys are not inferred.
  • Add result and plan coverage in asof_join.slt, plus a keyed selective workload as ASOF benchmark Q09.

What is the testing strategy for this PR?

The SLT cases check the mirrored plan, matching and unmatched results, a right-side IS NULL filter, and a left disjunction that must not prune the right input. Q09 runs the user-visible query with 50M rows per side across 500K groups, selecting one group.

Are there any user-facing changes?

No API or result changes. Eligible ASOF joins can sort and retain fewer right-side rows.

@github-actions github-actions Bot added optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt) labels Sep 21, 2026
@codecov-commenter

codecov-commenter commented Sep 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.07407% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.48%. Comparing base (92559af) to head (c7522fa).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/optimizer/src/push_down_filter.rs 74.07% 4 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25574      +/-   ##
==========================================
- Coverage   82.48%   82.48%   -0.01%     
==========================================
  Files        1140     1140              
  Lines      438540   438520      -20     
  Branches   438540   438520      -20     
==========================================
- Hits       361751   361722      -29     
- Misses      54946    54954       +8     
- Partials    21843    21844       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…r-mirroring

# Conflicts:
#	benchmarks/sql_benchmarks/asof_join/asof_join.suite
#	datafusion/optimizer/src/push_down_filter.rs
#	datafusion/sqllogictest/test_files/asof_join.slt
@Xuanwo
Xuanwo marked this pull request as ready for review September 24, 2026 12:57
@Xuanwo

Xuanwo commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

Hi @jayzhan211 and @2010YOUY01, this follow-up is now restacked and ready for review. Would you mind taking a look when you have time? Thanks!

@jayzhan211

Copy link
Copy Markdown
Contributor

Thanks @Xuanwo , one suggestion here

The hand-written col = literal match covers only one shape and duplicates infer_join_predicates_impl in this file. Every matching pair has equal keys, so any deterministic predicate over only left key columns also holds for the matching right keys. Reusing the helper also mirrors IN, ranges and OR on the key. The same-type check can go: coercion already wraps mismatched keys in CAST, so they are never direct columns. I tried this locally and asof_join.slt passes unchanged. It's fine to do this in a follow-up.

-                // A literal comparison on an equal, same-typed key has the
-                // same value for every matching pair. Mirroring it to the
-                // right can prune groups without changing the ASOF candidate.
-                let mut right_predicates = Vec::new();
-                for predicate in &push_predicates {
-                    ...
-                }
+                // Every matching pair has equal key values, so a deterministic
+                // predicate over left keys holds for the matching right keys.
+                let join_col_keys = join
+                    .on
+                    .iter()
+                    .filter_map(|(l, r)| Some((l.try_as_col()?, r.try_as_col()?)))
+                    .collect::<Vec<_>>();
+                let mut inferred = InferredPredicates::new(JoinType::Inner);
+                infer_join_predicates_impl::<true, false>(
+                    &join_col_keys,
+                    &push_predicates,
+                    &mut inferred,
+                )?;
+                let right_predicates = inferred
+                    .predicates
+                    .into_iter()
+                    .filter(|p| {
+                        p.column_refs()
+                            .iter()
+                            .all(|c| join.right.schema().is_column_from_schema(c))
+                    })
+                    .collect::<Vec<_>>();

With this change, both of these prune the right input as well:

SELECT * FROM l ASOF JOIN r MATCH_CONDITION (l.ts >= r.ts) ON l.k = r.k WHERE l.k IN (1, 3);
SELECT * FROM l ASOF JOIN r MATCH_CONDITION (l.ts >= r.ts) ON l.k = r.k WHERE l.k > 1;

@2010YOUY01 2010YOUY01 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. It's a good idea, and the change looks safe.

Comment thread datafusion/optimizer/src/push_down_filter.rs
@2010YOUY01

Copy link
Copy Markdown
Contributor

The hand-written col = literal match covers only one shape and duplicates infer_join_predicates_impl in this file. Every matching pair has equal keys, so any deterministic predicate over only left key columns also holds for the matching right keys. Reusing the helper also mirrors IN, ranges and OR on the key. The same-type check can go: coercion already wraps mismatched keys in CAST, so they are never direct columns. I tried this locally and asof_join.slt passes unchanged. It's fine to do this in a follow-up.

I think this util is a neat idea, but a bit hard to wrap my head around and get convinced it's safe to reuse; However we can implement something similar for ASOF joins.

@2010YOUY01

Copy link
Copy Markdown
Contributor

Performance

Q09 has 1M rows on each side across 10K equality groups. WHERE l.key = 42 retains one group and produces 100 rows with both revisions. On an Apple M4 Max, using release builds and two 7-iteration runs per revision, the median elapsed time across all 14 iterations was:

Revision Median elapsed
main without this optimization (b172f73f0) 21.5 ms
This change (b3fd5f1f9) 2.8 ms
This selective keyed workload is about 7.8x faster (87% lower elapsed time). The benchmark file and command were the same for both binaries:

target/release/benchmark_runner asof_join --query 9 --iterations 7

The branch was subsequently synced through 21a3215b6; those upstream-only commits do not touch the optimizer or ASOF paths exercised by Q09.

nit: We could scale this benchmark query up so a run takes around 1s. At ~2ms per run, the numbers are easily swayed by noise, e.g. a false-positive 2x slowdown.

@Xuanwo

Xuanwo commented Sep 25, 2026 •

Copy link
Copy Markdown
Member Author

Thanks @jayzhan211 and @2010YOUY01. I kept this PR focused and opened #25728 with an ASOF-specific key mapping.

I also scaled Q09 to about 1s on the baseline and reran it. The new median is 972.1ms vs 78.5ms across 14 runs.

@jayzhan211
jayzhan211 added this pull request to the merge queue Sep 26, 2026
@jayzhan211

Copy link
Copy Markdown
Contributor

Thanks @Xuanwo and @2010YOUY01 !

Merged via the queue into apache:main with commit d6cb2c5 Sep 26, 2026
41 of 42 checks passed
@Xuanwo
Xuanwo deleted the xuanwo/asof-key-filter-mirroring branch September 26, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants